package org.poptest.model;

import org.poptest.Callback;
import org.poptest.reflect.MemberCallback;
import org.poptest.reflect.ReflectionUtils;
import org.poptest.util.AssertInternal;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

/**
 * @author Alan Roche
 *         Date: 05/02/11
 *         Time: 22:10
 */
public class TestInstance {

    private final Object instance;
    /**
     * A list of fields in the class, in order of declaration, from superclass to subclass
     */
    private final List<FieldNode> fieldNodes;
    private final Class<?> testClass;

    public TestInstance(final Object instance) {
        this.instance = instance;
        this.testClass = instance.getClass();
        fieldNodes = initFieldNodes();
    }

    /**
     * This constructor allows test class
     *
     * @param testClass
     */
    public TestInstance(final Class<?> testClass) {
        this.testClass = testClass;
        instance = null;
        fieldNodes = initFieldNodes();
    }

    public Class<?> getTestClass() {
        return instance != null ? instance.getClass() : testClass;
    }

    public Object getInstance() {
        return instance;
    }

    private List<FieldNode> initFieldNodes() {
        final List<FieldNode> fieldNodes = new ArrayList<FieldNode>();
        for (final Field field : getTestClass().getDeclaredFields()) {
            final FieldNode fieldNode = new FieldNode(this, field);
            fieldNodes.add(fieldNode);
        }
        return Collections.unmodifiableList(fieldNodes);
    }


    public List<FieldNode> getFieldNodes() {
        return fieldNodes;
    }


    /**
     * Returns an iterator indexed just before  fieldNode.
     * Calling previous)_ returns the item before fieldNode.
     * Calling next() returns this node
     */
    public ListIterator<FieldNode> fieldIterator(final FieldNode fieldNode) {
        final ListIterator<FieldNode> iterator = fieldNodes.listIterator();
        boolean matchFound = false;
        while (iterator.hasNext()) {
            final FieldNode fieldNodeItem = iterator.next();
            // Check for same, not equals
            if (fieldNodeItem.equals(fieldNode)) {
                matchFound = true;
                break;
            }
        }
        AssertInternal.isTrue(matchFound, "fieldNodes does not contain fieldNode: " + fieldNode.getName());
        if (iterator.hasPrevious()) {
            iterator.previous();
        }
        return iterator;
    }


    private List<FieldNode> initFields() {
        final List<FieldNode> nodesList = new ArrayList<FieldNode>();
        ReflectionUtils.foreachField(getInstance(), new MemberCallback<Field>() {
            public void execute(final Field field) {
                nodesList.add(new FieldNode(TestInstance.this, field));
            }
        });
        return nodesList;

    }

    public void foreachField(final Callback<FieldNode> callback) {
        AssertInternal.notNull(instance, "instance is null, is this beforeClass or some other static method hook?");

        for (final FieldNode fieldNode : fieldNodes) {
            callback.execute(fieldNode);
        }
    }
}
